
const EmptyTransitionArray:Transition<any>[] = []
const EmptyTriggerTransition:Map<string,Transition<any>[]> = new Map()

export type StateMachineRoot = StateMachine<unknown,unknown>

export class StateBase<IDType> {
    id:IDType
    fsm:StateMachine<unknown,IDType>
    constructor(public needExitTime:boolean) { }
    onInit(){}
    onEnter(){}
    onLogicUpdate(){}
    onUpdate(){}
    onExit(){}
    onRequestExit(){}
}

export class StateMachine<OwnIDType,StateIDType> extends StateBase<OwnIDType>{
    activeState:StateBase<StateIDType>
    activeTransitions:Transition<StateIDType>[]
    activeTriggerTransitions:Map<string,Transition<StateIDType>[]>

    get activeStateID():StateIDType|undefined{
        if(!this.activeState) return undefined
        return this.activeState.id
    }

    private startState:StateBase<StateIDType> // 当前运行的节点
    private pendingStateID:StateIDType|undefined // 等待转换的节点
    private name2StateBundle:Map<StateIDType,StateBundle<StateIDType>> = new Map()
    private triggerFromAny:Map<string,Transition<StateIDType>[]> = new Map()

    onInit(){
        // 不初始化子状态机
        if(this.fsm) return
        // 逻辑错误!
        if(!this.startState) return;

        this.onEnter()
    }

    onEnter() {
        this.changeState(this.startState.id)
    }

    onExit() {
        this.activeState?.onExit()
        this.activeState = undefined
    }

    onLogicUpdate(){
        if(!this.activeState) return
        for (let activeTransition of this.activeTransitions) {
            if(this.tryTransition(activeTransition)) {
                break // 转换条件达成
            }
        }
        this.activeState.onLogicUpdate()
    }

    onUpdate(): void {
        if(!this.activeState) return
        this.activeState.onUpdate()
    }

    // state需在此集合下
    resetStartState(state:StateBase<StateIDType>){
        this.startState = state
    }

    addState(id:StateIDType,state:StateBase<StateIDType>){
        state.fsm = this
        state.id = id
        state.onInit()

        let bundle = this.getStateBundle(id)
        bundle.state = state

        if(!this.startState) this.startState = state
    }

    // 替换状态(其实就是add🤣)
    replaceState(id:StateIDType,state:StateBase<StateIDType>){
        this.addState(id,state)
    }

    // 添加节点之间的过度
    addTransition(transition:Transition<StateIDType>){
        transition.fsm = this
        let bundle = this.getStateBundle(transition.from)
        bundle.addTransition(transition)
    }

    // 添加触发型的节点过度
    addTriggerTransition(trigger:string,transition:Transition<StateIDType>){
        let bundle = this.getStateBundle(transition.from)
        bundle.addTriggerTransition(trigger, transition)
    }

    // fsm内任意节点过度到任意节点的过度
    addTriggerFromAny(trigger:string,transition:Transition<StateIDType>){
        transition.fsm = this

        if(!this.triggerFromAny.get(trigger)){
            this.triggerFromAny.set(trigger,[])
        }
        this.triggerFromAny.get(trigger).push(transition)
    }

    // 触发trigger
    trigger(trigger:string){
        // 转换成功
        this.tryTrigger(trigger)
    }

    // 节点内通知fsm可退出自身
    canExitCurrent(){
        if(this.pendingStateID != undefined){
            this.changeState(this.pendingStateID)
            this.pendingStateID = undefined
        }else{
            this.fsm?.canExitCurrent()
        }
    }

    private tryTrigger(trigger:string):boolean{
        if(!this.activeState) return
        // 优先走状态内的trigger
        if(this.activeTriggerTransitions.has(trigger)){
            let array = this.activeTriggerTransitions.get(trigger)
            for (let transition of array) {
                if(this.tryTransition(transition)) return true
            }
        }
        // any State
        if(this.triggerFromAny.has(trigger)){
            for (let transition of this.triggerFromAny.get(trigger)) {
                if(this.tryTransition(transition)) return true
            }
        }

        return false
    }

    private tryTransition(transition:Transition<StateIDType>):boolean{
        if(!transition.shouldTransition()) return false

        this.requestChangeState(transition.to, transition.forceExit)
        return true
    }

    private requestChangeState(targetID:StateIDType,forceExit:boolean){
        if(!this.activeState.needExitTime || forceExit){
            this.changeState(targetID)
            return
        }
        this.pendingStateID = targetID
        this.activeState.onRequestExit()
    }

    private changeState(targetID:StateIDType){
        this.activeState?.onExit()

        if(!this.name2StateBundle.has(targetID)) return

        let bundle = this.name2StateBundle.get(targetID)
        if(!bundle.state) return;

        this.activeTransitions = bundle.transitions ?? EmptyTransitionArray
        this.activeTriggerTransitions = bundle.trigger2Transition ?? EmptyTriggerTransition
        this.activeState = bundle.state
        this.activeState.onEnter()
    }

    // 获取状态节点
    private getStateBundle(id:StateIDType):StateBundle<StateIDType>{
        if(!this.name2StateBundle.has(id)){
            this.name2StateBundle.set(id,new StateBundle<StateIDType>())
        }
        return this.name2StateBundle.get(id)
    }
}

// 将一个状态与多个转换条件封装起来
export class StateBundle<IDType>{
    state:StateBase<IDType>
    transitions:Transition<IDType>[]
    trigger2Transition:Map<string,Transition<IDType>[]>

    addTransition(transition:Transition<IDType>) {
        this.transitions = this.transitions ?? []
        this.transitions.push(transition)
    }

    addTriggerTransition(trigger:string,transition:Transition<IDType>){
        this.trigger2Transition = this.trigger2Transition ?? new Map()

        if(!this.trigger2Transition.has(trigger)){
            this.trigger2Transition.set(trigger,[])
        }
        this.trigger2Transition.get(trigger).push(transition)
    }
}

export class Transition<IDType> {
    fsm:StateMachine<unknown,IDType>

    constructor(
        public from:IDType,
        public to:IDType,
        public condition?:(transition:Transition<IDType>)=>boolean,
        public forceExit:boolean = false
    ) { }

    shouldTransition():boolean{
        if(!this.condition) return true
        return this.condition(this)
    }
}